/******************************************************************************/
/* compile/operations.c                                            2015-03-21 */
/* Projet Systèmes Informatiques - INSA Toulouse                 Félix Poisot */
/******************************************************************************/
#include "operations.h"
#include "output.h"
#include "compile.h"
#include <stdlib.h>
#include <stdio.h>
/******************************************************************************/

TypedAddr declare(Type t, char* name)
{
	TypedAddr res;

	Addr addr = tabSymboles_push(t, name, gDeclGlobal, gLineNo);
   free(name);

	res.addr = addr;
	res.type = t;
	res.mode = ADDR_IMMEDIAT;
	return res;
}

void declare_table(Type t, char* name, int taille)
{
   int rc;
	if (taille <= 0)
      cerror("Invalid array size for '%s'", name);

   tabSymboles_pushArray(t, taille, name, gDeclGlobal, gLineNo);
   free(name);
}


TypedAddr affectation(TypedAddr a1, TypedAddr a2, const char* op)
{  
   int t_ok = type_canAffect(a1.type, a2.type);
   if (t_ok != 0) 
   {
      type_setConst(&a2.type, 0); // for clarity
      char* n1 = type_repr(a1.type);
      char* n2 = type_repr(a2.type);
      if (t_ok == -1)
         cerror("Left-hand operand for '%s' is 'const'", op);
      else if (t_ok == -3)
         cerror("Affectation '%s' -> '%s' discards const qualifier", n2, n1);
      else
         cerror("Cannot assign value of type '%s' to a variable of type '%s'", n2, n1);
      free(n2);
      free(n1);
      exit(-1);
   }

	if (a1.mode != ADDR_IMMEDIAT)
		chk_init(a1.addr); // empêche d'écrire dans un pointeur indéfini.
	chk_init(a2.addr);

	if (tabSymboles_isTmp(a2.addr))
		tabSymboles_popTmp();

   // optimization of COPs
   if (tabSymboles_isTmp(a2.addr) && a1.mode == ADDR_IMMEDIAT 
         && !output_justUsedChangeDest()) 
   {
      // There is at least 1 operation writing to the temporary: change my mind 
      // about destination of last operation 
      output_changeDest(a1.addr);
   }
   else {
		if (a1.mode == ADDR_INDIRECT && a2.mode == ADDR_INDIRECT)
			out(CPI, a1.addr, a2.addr);
		else if (a1.mode == ADDR_INDIRECT && a2.mode == ADDR_IMMEDIAT) {
			out(STI, a2.addr, a1.addr);
         // Juste pour interdire d'essayer plus tard dans une chaine
         // d'affectations. Bugfix magique pour post++.
         output_changeDest(a2.addr);
		} else if (a1.mode == ADDR_IMMEDIAT && a2.mode == ADDR_INDIRECT)
			out(LDI, a1.addr, a2.addr);
		else
			out(COP, a1.addr, a2.addr);
	}

   // Sans effet sur variables temporaires
   tabSymboles_flagInit(a1.addr);
   
   return a1;
}

Addr use_var(char* name, Type* pType)
{  
	Addr addr = tabSymboles_find(name, pType);
	if (addr.addr == -1) 
   {
      // TODO remplacer ce bouchon par quelque chose permettant l'appel
      // (difficile car l'adresse de saut n'est peut-être pas encore connue).
      if (tabFonction_get(name) != -1) {
         *pType = type_fonction();
         free(name);
         return addr;
      }
      // else: pas variable ni fonction.
         
		cerror("undeclared symbol '%s'", name);
   }
   // found variable
	free(name);

   if (type_knownValue(*pType)) { // array
      Addr t = tabSymboles_pushTmp();
      out(AFC, t, addr);
      if (addr.space == LOCAL) { // les pointeurs sont toujours absolus
         Addr sp = {0, GLOBAL};
         out(ADD, t, t, sp);
      }
      addr = t;
      // not propagated: the operations would have to handle constant folding (too complex)
      type_setKnownValue(pType, 0); 
   }

	return addr; 
}

void chk_init(Addr addr)
{
	if (!tabSymboles_isInit(addr)) {
		cerror("Variable '%s' used before initialization",
				  tabSymboles_nameByAddr(addr));
	}
}

void print(TypedAddr a)
{
	TypedAddr me = a;

   if (a.type.indirection == -1)
      cerror("Pointers to functions are not supported yet");
   
	if (tabSymboles_isTmp(a.addr))
		tabSymboles_popTmp();
	else
		chk_init(a.addr);

	if (a.mode == ADDR_INDIRECT) {
		me.addr = tabSymboles_pushTmp();
		out(LDI, me.addr, a.addr);
		out(PRI, me.addr);
		tabSymboles_popTmp();
	}
	else
		out(PRI, a.addr);
}

TypedAddr followPtr(TypedAddr a)
{
   TypedAddr res = a;

   if (a.type.indirection == -1) // function
      cerror("Functions are not dereferencable");
   
   chk_init(a.addr);
   
   if (!a.type.indirection)
      cerror("Cannot dereference expression: not a pointer");

	res.type.indirection--;

	if (a.mode == ADDR_IMMEDIAT)
		res.mode = ADDR_INDIRECT; // and keep same address
	else {
		if (!tabSymboles_isTmp(a.addr))
			res.addr = tabSymboles_pushTmp();
		out(LDI, res.addr, a.addr);
	}

   return res;
}

TypedAddr binop(asm_Operation op, const char* sop, TypedAddr a1, TypedAddr a2)
{
   TypedAddr res;
   int has_r = 0;
   int mustFree = 0; // # of 'consumed' temporary variables
   
   char* n1; // for error output
   char* n2; // 

   if (a1.type.indirection == -1 || a2.type.indirection == -1)
      cerror("Pointers to functions are not supported yet");
   
	/* May have to fetch inputs */
	if (a1.mode == ADDR_INDIRECT) {
		Addr na = (tabSymboles_isTmp(a1.addr)) ? (a1.addr) : (tabSymboles_pushTmp());
		out(LDI, na, a1.addr);
		a1.mode = ADDR_IMMEDIAT;
		a1.addr = na;
	}
	if (a2.mode == ADDR_INDIRECT) {
		Addr na = (tabSymboles_isTmp(a2.addr)) ? (a2.addr) : (tabSymboles_pushTmp());
		out(LDI, na, a2.addr);
		a2.mode = ADDR_IMMEDIAT;
		a2.addr = na;
	}

   /* If input is a temporary, re-use it. Note than addresses of a1 and a2 will
    * not always be ordered, but temporary variables will always have addresses 
    * greater than stack variables. */
   if (tabSymboles_isTmp(a1.addr)) {
      res.addr = a1.addr;
      has_r = 1;
   } else 
      chk_init(a1.addr);
   if (tabSymboles_isTmp(a2.addr)) {
      if (!has_r) { 
         res.addr = a2.addr;
         has_r = 1;
      }
      else // assume @a1 < @a2 in this case -> use @a1
         ++mustFree;
   } else 
      chk_init(a2.addr);
   if (!has_r) // can't overwrite any of the input vars
      res.addr = tabSymboles_pushTmp();
   
   /*** Type combination rules ***/
   if (op == ADD)
   {
      if (a1.type.indirection && a2.type.indirection)
         goto typeError;
      else if (a1.type.indirection) // and a2 integer
         res.type = a1.type;
      else // either int + ptr or int + int
         res.type = a2.type;
   }
   else if (op == SOU)
   {
      if (type_canCompare(a1.type, a2.type))
         res.type = type_entier(1);
      else if (a1.type.indirection && a2.type.indirection)
         goto typeError;
      else if (a1.type.indirection) // and a2 integer
         res.type = a1.type;
      else // int - ptr
         goto typeError;
   }
   else if (op == MUL || op == DIV || op == MOD)
   {
      if (a1.type.indirection || a2.type.indirection)
         goto typeError;
      res.type = a1.type;
   }
   else // comparison
   {
      if (!type_canCompare(a1.type, a2.type))
         goto typeError;
      res.type = type_entier(1);
   }

   /*** OP output ***/
   // ! Possibilité d'aliasing entre res et a1/a2 !
   Addr t1 = tabSymboles_pushTmp();
   ++mustFree;
   
   if (op == MOD) {
      out(DIV, t1, a1.addr, a2.addr);
      out(MUL, t1, t1, a2.addr);
      out(SOU, res.addr, a1.addr, t1);
   }
   else if (op >= 0x100 && op < 0x200) { // NOT qqch
      out(op - 0x100, res.addr, a1.addr, a2.addr);
      out(AFC, t1, 0);
      out(EQU, res.addr, res.addr, t1);
   }
   else {
      out(op, res.addr, a1.addr, a2.addr);
   }
   
   for (; mustFree; --mustFree)
      tabSymboles_popTmp();
   
   type_setConst(&res.type, 1);
   res.mode = ADDR_IMMEDIAT;
   return res;
   
typeError:
      n1 = type_repr(a1.type);
      n2 = type_repr(a2.type);
      cerror("Operator '%s' not valid between types '%s' and '%s'\n", sop, n1, n2);
      free(n2);
      free(n1);
      exit(-1);
}

TypedAddr unop(asm_Operation op, int post, TypedAddr a)
{
   const char* name = (op == SOU) ? ("--") : ("++");
   TypedAddr tmp;
   tmp.addr = tabSymboles_pushTmp();
   tmp.type = type_entier(1);
   tmp.mode = ADDR_IMMEDIAT;
	out(AFC, tmp.addr, 1);
   
   if (post) { //annoying mode
      TypedAddr res;
      res.addr = tabSymboles_pushTmp();
      res.type = a.type;
      res.mode = ADDR_IMMEDIAT;
      affectation(res, a, name);
      tabSymboles_protectTmp(res.addr, 1); // res must not be consumed by binop
      affectation(a, binop(op, name, res, tmp), name);
      tabSymboles_protectTmp(res.addr, 0);
      return res;
   }
   else { //easy mode
      return affectation(a, binop(op, name, a, tmp), name);
   }
}


void consolidate()
{
   // base de pile (début des variables locales): juste après les variables globales.
   Addr bp;
   bp.addr = 0;
   bp.space = GLOBAL;
   out_at(0, AFC, bp, tabSymboles_nbOctetsVariablesGlobales());
   
   int funID;
   int addrFunD, addrFunF, addrFin;
   
   // Le code d'initialisation des variables globales doit être exécuté au début
   // du programme, avant le main. Le code du main doit ensuite être exécuté. On
   // saute par dessus toutes les autres fonctions du programme.
   
   output_later(); // pour le saut. Va remonter (avec tout le code dispersé entre les fonctions)
   while ((funID = tabFonction_findLastGap(&addrFunD, &addrFunF, &addrFin)) != -1)
   {
      output_inverse(addrFunD, addrFunF, addrFin);
      tabFonction_corpsMoved(funID, addrFin - addrFunF);
   }

   // le JMP d'entrée est à sa place définitive
   int jmpMainPos = addrFunD + (addrFin - addrFunF) - 1;
   
   // Le main doit êre à la fin: un saut 'return' n'a nulle part où aller, et la
   // machine détecte l'exécution l'instruction 1-après-la-fin pour savoir que 
   // le programme s'est terminé normalement.   
   funID = tabFonction_getCorps("main", &addrFunD, &addrFunF);
   if (funID == -1)
      cerror("'main' fonction was not defined");
   output_inverse(addrFunD, addrFunF, output_cur());
   Addr a = {addrFunD + (output_cur() - addrFunF), GLOBAL};
   out_at(jmpMainPos, JMP, a);
   
}
